#include "virtualdisk.h"
#include <QFile>
#include <QDataStream>
#include <QDateTime>
#include <cstring>

VirtualDisk::VirtualDisk(QString fname) : filename(fname) {
    for (int i = 0; i < NUM_BLOCKS; i++) {
        fat[i] = EMPTY_BLOCK;
    }

    for (int i = 0; i < NUM_BLOCKS; i++) {
        memset(blocks[i], 0, BLOCK_SIZE);
    }
}

QString VirtualDisk::getCurrentTime() {
    QDateTime now = QDateTime::currentDateTime();
    return now.toString("yyyyMMddhhmmss");
}

QVector<FCB> VirtualDisk::readFCBsFromBlock(int blockNum) {
    QVector<FCB> fcbs;
    if (blockNum < 0 || blockNum >= NUM_BLOCKS) return fcbs;

    for (int i = 0; i < MAX_FCB_PER_BLOCK; i++) {
        FCB fcb;
        memcpy(&fcb, &blocks[blockNum][i * FCB_SIZE], FCB_SIZE);
        if (fcb.type != 0) {
            fcbs.push_back(fcb);
        }
    }
    return fcbs;
}

void VirtualDisk::writeFCBsToBlock(int blockNum, const QVector<FCB>& fcbs) {
    if (blockNum < 0 || blockNum >= NUM_BLOCKS) return;

    memset(blocks[blockNum], 0, BLOCK_SIZE);

    for (int i = 0; i < fcbs.size() && i < MAX_FCB_PER_BLOCK; i++) {
        memcpy(&blocks[blockNum][i * FCB_SIZE], &fcbs[i], FCB_SIZE);
    }
}

bool VirtualDisk::create() {
    QFile file(filename);
    if (file.exists()) return false;

    fat[0] = LAST_BLOCK;
    QVector<FCB> rootFCBs;
    writeFCBsToBlock(0, rootFCBs);

    return save();
}

bool VirtualDisk::load() {
    QFile file(filename);
    if (!file.open(QIODevice::ReadOnly)) return false;

    QDataStream in(&file);
    in.readRawData((char*)fat, FAT_SIZE);

    for (int i = 0; i < NUM_BLOCKS; i++) {
        in.readRawData(blocks[i], BLOCK_SIZE);
    }

    file.close();
    return true;
}

bool VirtualDisk::save() {
    QFile file(filename);
    if (!file.open(QIODevice::WriteOnly)) return false;

    QDataStream out(&file);
    out.writeRawData((char*)fat, FAT_SIZE);

    for (int i = 0; i < NUM_BLOCKS; i++) {
        out.writeRawData(blocks[i], BLOCK_SIZE);
    }

    file.close();
    return true;
}

int VirtualDisk::findEmptyBlock() {
    for (int i = 0; i < NUM_BLOCKS; i++) {
        if (fat[i] == EMPTY_BLOCK) {
            return i;
        }
    }
    return -1;
}

QVector<int> VirtualDisk::allocateBlocks(int count) {
    QVector<int> result;
    QVector<int> candidates;

    for (int i = 0; i < NUM_BLOCKS; i++) {
        if (fat[i] == EMPTY_BLOCK) {
            candidates.push_back(i);
            if (candidates.size() >= count) break;
        }
    }

    if (candidates.size() < count) return result;

    for (int i = 0; i < count; i++) {
        result.push_back(candidates[i]);
        if (i < count - 1) {
            fat[candidates[i]] = candidates[i + 1];
        } else {
            fat[candidates[i]] = LAST_BLOCK;
        }
    }

    return result;
}

void VirtualDisk::freeBlocks(int first_block) {
    int current = first_block;
    while (current != LAST_BLOCK && current >= 0 && current < NUM_BLOCKS) {
        int next = fat[current];
        fat[current] = EMPTY_BLOCK;
        current = next;
    }
}

FCB VirtualDisk::findFCB(int dir_block, QString name) {
    FCB emptyFCB;
    QVector<FCB> fcbs = readFCBsFromBlock(dir_block);

    if (name.length() > 8) {
        name = name.left(8);
    }

    for (const FCB& fcb : fcbs) {
        if (QString(fcb.name) == name) {
            return fcb;
        }
    }
    return emptyFCB;
}

bool VirtualDisk::addFCB(int dir_block, FCB fcb) {
    QVector<FCB> fcbs = readFCBsFromBlock(dir_block);

    for (const FCB& existing : fcbs) {
        if (QString(existing.name) == QString(fcb.name) && existing.type != 0) {
            return false;
        }
    }

    if (fcbs.size() >= MAX_FCB_PER_BLOCK) {
        return false;
    }

    fcbs.push_back(fcb);
    writeFCBsToBlock(dir_block, fcbs);
    return true;
}

bool VirtualDisk::removeFCB(int dir_block, QString name) {
    QVector<FCB> fcbs = readFCBsFromBlock(dir_block);

    if (name.length() > 8) {
        name = name.left(8);
    }

    for (FCB& fcb : fcbs) {
        if (QString(fcb.name) == name) {
            fcb.type = 0;
            writeFCBsToBlock(dir_block, fcbs);
            return true;
        }
    }
    return false;
}

QVector<FCB> VirtualDisk::listFCBs(int dir_block) {
    return readFCBsFromBlock(dir_block);
}

bool VirtualDisk::createDir(int parent_block, QString name) {
    if (name.length() > 8) {
        name = name.left(8);
    }

    FCB existing = findFCB(parent_block, name);
    if (existing.type != 0) {
        return false;
    }

    int new_block = findEmptyBlock();
    if (new_block == -1) {
        return false;
    }
    fat[new_block] = LAST_BLOCK;

    QVector<FCB> newDirFCBs;
    writeFCBsToBlock(new_block, newDirFCBs);

    FCB dirFCB;
    strncpy(dirFCB.name, name.toUtf8().constData(), 8);
    dirFCB.name[8] = '\0';
    dirFCB.size = BLOCK_SIZE;
    dirFCB.first_block = new_block;
    dirFCB.type = 2;
    strncpy(dirFCB.datetime, getCurrentTime().toUtf8().constData(), 14);

    return addFCB(parent_block, dirFCB);
}

bool VirtualDisk::deleteDir(int parent_block, QString name) {
    if (name.length() > 8) {
        name = name.left(8);
    }

    FCB dirFCB = findFCB(parent_block, name);
    if (dirFCB.type != 2) {
        return false;
    }

    QVector<FCB> childFCBs = listFCBs(dirFCB.first_block);
    if (!childFCBs.isEmpty()) {
        return false;
    }

    freeBlocks(dirFCB.first_block);
    return removeFCB(parent_block, name);
}

bool VirtualDisk::createFile(int parent_block, QString name, int size) {
    if (name.length() > 8) {
        name = name.left(8);
    }

    FCB existing = findFCB(parent_block, name);
    if (existing.type != 0) {
        return false;
    }

    int blocks_needed = (size + BLOCK_SIZE - 1) / BLOCK_SIZE;
    if (blocks_needed <= 0) blocks_needed = 1;

    QVector<int> allocated_blocks = allocateBlocks(blocks_needed);
    if (allocated_blocks.isEmpty()) {
        return false;
    }

    FCB fileFCB;
    strncpy(fileFCB.name, name.toUtf8().constData(), 8);
    fileFCB.name[8] = '\0';
    fileFCB.size = size;
    fileFCB.first_block = allocated_blocks[0];
    fileFCB.type = 1;
    strncpy(fileFCB.datetime, getCurrentTime().toUtf8().constData(), 14);

    return addFCB(parent_block, fileFCB);
}

bool VirtualDisk::deleteFile(int parent_block, QString name) {
    if (name.length() > 8) {
        name = name.left(8);
    }

    FCB fileFCB = findFCB(parent_block, name);
    if (fileFCB.type != 1) {
        return false;
    }

    freeBlocks(fileFCB.first_block);
    return removeFCB(parent_block, name);
}

QString VirtualDisk::getInfo() {
    QString info = "Virtual Disk Information:\n";
    info += "==========================\n\n";

    info += "FAT16 Table:\n";
    for (int i = 0; i < NUM_BLOCKS; i++) {
        info += QString("%1 ").arg(fat[i], 4, 16, QChar('0')).toUpper();
    }
    info += "\n\n";

    for (int i = 0; i < NUM_BLOCKS; i++) {
        info += QString("Block %1:\n").arg(i);

        if (fat[i] == EMPTY_BLOCK) {
            info += "  Empty block\n";
        } else {
            bool isDir = false;
            QVector<int> dirBlocks;
            dirBlocks.push_back(0);

            for (int j = 0; j < dirBlocks.size(); j++) {
                int dirBlock = dirBlocks[j];
                QVector<FCB> fcbs = readFCBsFromBlock(dirBlock);
                for (const FCB& fcb : fcbs) {
                    if (fcb.type == 2 && fcb.first_block == i) {
                        isDir = true;
                        break;
                    }
                    if (fcb.type == 2) {
                        dirBlocks.push_back(fcb.first_block);
                    }
                }
                if (isDir) break;
            }

            if (isDir) {
                info += "  Directory block\n";
                QVector<FCB> fcbs = readFCBsFromBlock(i);
                if (fcbs.isEmpty()) {
                    info += "  No entries in this directory\n";
                } else {
                    info += "  Entries:\n";
                    for (const FCB& fcb : fcbs) {
                        info += QString("    Name: %1, Type: %2, Size: %3, First Block: %4\n")
                        .arg(fcb.name)
                            .arg(fcb.type == 1 ? "File" : "Dir")
                            .arg(fcb.size)
                            .arg(fcb.first_block);
                    }
                }
            } else {
                info += "  Data block\n";
            }

            info += QString("  Next block: %1\n").arg(fat[i] == LAST_BLOCK ? "None" : QString::number(fat[i]));
        }
        info += "\n";
    }

    return info;
}
